#include "SQLiteSession.h"
#include "../FilterSDK/ia.h"

#define SQLITE_STATUS_CHECK	{ if (status != SQLITE_OK ) { IA_ERROR("sqlite error status %d: %s", status, pMsg);  sqlite3_free(pMsg); return(IA_L_ERR); }}

CSQLiteSession::CSQLiteSession()
{
	_pConn = NULL;
}

CSQLiteSession::~CSQLiteSession()
{
	Close();
}

int CSQLiteSession::Connect(CDBConfig config)
{
	_dbConfig = config;

	IA_INFO("Open SQLite database %s, ver %s, threadSafe=%d", _dbConfig.DBName.c_str(), SQLITE_VERSION, sqlite3_threadsafe());

	//@see https://www.sqlite.org/c3ref/open.html
	//The database is opened for readingand writing, and is created if it does not already exist.
	char* pMsg = NULL;
	int status = sqlite3_open(config.DBName.c_str(), &_pConn);
	SQLITE_STATUS_CHECK

	status = sqlite3_busy_timeout(_pConn, config.Timeout);
	SQLITE_STATUS_CHECK

	IA_RETURN_OK
}

int CSQLiteSession::Close() noexcept
{
	if (NULL != _pConn) {
		IA_INFO("Close SQLite database %s", _dbConfig.DBName.c_str());
		sqlite3_close(_pConn);
	}

	_pConn = NULL;

	IA_RETURN_OK
}

int CSQLiteSession::IsConnected()
{
	if (_pConn != NULL) {
		IA_RETURN_TRUE
	}

	IA_RETURN_FALSE
}

int CSQLiteSession::GetTables(std::vector<std::string>& tables)
{
	int col = 0;
	int row = 0;
	std::vector<std::string> results;

	int status = ExcuteSelect("SELECT name from sqlite_master WHERE type='table'", results, col, row);
	STATUS_CHECK

	for (int i = 1; i < row; i++) {
		tables.push_back(results[i]);
	}

	IA_RETURN_OK
}

int CSQLiteSession::BeginTransaction()
{
	char* pMsg = NULL;

	int status = sqlite3_exec(_pConn, "begin transaction;", NULL, NULL, &pMsg);
	SQLITE_STATUS_CHECK

	IA_RETURN_OK
}

int CSQLiteSession::CommitTransaction()
{
	char* pMsg = NULL;

	int status = sqlite3_exec(_pConn, "commit;", NULL, NULL, &pMsg);
	SQLITE_STATUS_CHECK

	IA_RETURN_OK
}

int CSQLiteSession::RollBackTransaction()
{
	char* pMsg = NULL;

	int status = sqlite3_exec(_pConn, "rollback;", NULL, NULL, &pMsg);
	SQLITE_STATUS_CHECK

	IA_RETURN_OK
}

int CSQLiteSession::ExecuteScalar(std::string sql, std::string& result)
{
	int col = 0;
	int row = 0;
	std::vector<std::string> results;

	int status = ExcuteSelect(sql, results, col, row);
	STATUS_CHECK

	IA_ASSERT(col == 1 && row == 2);
	result = results[1];

	IA_RETURN_OK
}

int CSQLiteSession::ExcuteSelect(std::string sql, std::vector<std::string>& results, int& col, int& row)
{
	char** pResult = NULL;
	char* pMsg = NULL;

	int status = sqlite3_get_table(_pConn, sql.c_str(), &pResult, &row, &col, &pMsg);
	SQLITE_STATUS_CHECK

	IA_DEBUG("SQLite select ended with %d rows and %d cols", row, col);

	// Note: Since the first row is header, we have "row +1" of data actually
	for (int i = 0; i < row + 1; i++) {
		for (int j = 0; j < col; j++) {
			//IA_DEBUG("pResult[%d]=%-5s", i * col + j, pResult[i*col + j]);
			char* pStr = pResult[i * col + j];
			results.push_back(pStr == NULL ? "" : pStr);
		}
	}

	sqlite3_free_table(pResult);

	row += 1;
	IA_RETURN_OK
}

int CSQLiteSession::ExcuteNonQuery(std::string sql)
{
	char* pMsg = NULL;

	int status = sqlite3_exec(_pConn, sql.c_str(), NULL, NULL, &pMsg);
	SQLITE_STATUS_CHECK

	IA_RETURN_OK
}
